#ifndef TYPE_HPP
#define TYPE_HPP

#include <string>
#include <map>
#include <vector>
using namespace std;

enum AllTypes {
    TYPE_INT,
    TYPE_BOOL,
    TYPE_STRING,
    TYPE_CHAR,
    TYPE_DOUBLE,

    // seperator: types before this are primitives.
    TYPE_PRIMITIVE, 
    TYPE_DEPENDENT,

    TYPE_ARROW,
    TYPE_ARRAY,
    TYPE_LIST,
    TYPE_TUPLE,
    TYPE_RECORD,

    TYPE_POLY,
    TYPE_CONSTRUCTOR,

    TYPE_FUNC,
    TYPE_APPLY,
    TYPE_VAR,

    TYPE_ANY,

    TYPE_GENERIC_FUNC,
    TYPE_GENERIC_VAR,
    TYPE_GENERIC_TRAIT,
};

enum OperatorType {
    OPERATOR_ADD,
    OPERATOR_SUB,
    OPERATOR_MUL,
    OPERATOR_DIV,
    OPERATOR_SHIFT,
};

class Type {
protected:
    AllTypes _type_code;

public:
    Type(AllTypes t) : _type_code(t) {}
    virtual ~Type() {}

    virtual bool support_operator(OperatorType ot) {
        return false;
    }

    bool is_primitive() {
        return _type_code < TYPE_PRIMITIVE;
    }

    bool is_arrow() {
        return _type_code == TYPE_ARROW;
    }

    bool is_array() {
        return _type_code == TYPE_ARRAY;
    }

    bool is_type_con() {
        return _type_code == TYPE_CONSTRUCTOR;
    }

    bool is_type_func() {
        return _type_code == TYPE_FUNC;
    }

    bool is_type_var() {
        return _type_code == TYPE_VAR;
    }

    bool is_type_apply() {
        return _type_code == TYPE_APPLY;
    }

    bool is_generic_func() {
        return _type_code == TYPE_GENERIC_FUNC;
    }

    virtual const char* to_string() {
        return "";
    }

    virtual bool equals(Type* t) {
        return false;
    }

    virtual Type* try_apply(Type* t) {
        return nullptr;
    }
};

class PrimitiveType : public Type {
public:
    PrimitiveType(AllTypes t) : Type(t) {}

    virtual const char* to_string() {
        return "";
    }

    virtual bool equals(Type* t) {
        return this == t;
    }
};

class IntType : public PrimitiveType {
private:
    IntType() : PrimitiveType(TYPE_INT) {}

public:
    static IntType* get_instance() {
        static IntType instance;
        return &instance;
    }

    virtual const char* to_string() {
        return "Int";
    }

    virtual bool support_operator(OperatorType ot);
};

class BoolType : public PrimitiveType {
private:
    BoolType() : PrimitiveType(TYPE_BOOL) {}

public:
    static BoolType* get_instance() {
        static BoolType instance;
        return &instance;
    }

    virtual const char* to_string() {
        return "Bool";
    }
};

class DoubleType : public PrimitiveType {
private:
    DoubleType(): PrimitiveType(TYPE_DOUBLE) {}

public:
    static DoubleType* get_instance() {
        static DoubleType instance;
        return &instance;
    }

    virtual const char* to_string() {
        return "Double";
    }
};

class UnitType : public PrimitiveType {
private:
    UnitType() : PrimitiveType(TYPE_BOOL) {}

public:
    static UnitType* get_instance() {
        static UnitType instance;
        return &instance;
    }

    virtual const char* to_string() {
        return "Unit";
    }
};

class StringType : public PrimitiveType {
private:
    StringType() : PrimitiveType(TYPE_STRING) { }

public:
    static StringType* get_instance() {
        static StringType instance;
        return &instance;
    }

    virtual const char* to_string() {
        return "String";
    }

    virtual bool support_operator(OperatorType ot);
};

class CharType : public PrimitiveType {
private:
    CharType() : PrimitiveType(TYPE_CHAR) {}

public:
    static CharType* get_instance() {
        static CharType instance;
        return &instance;
    }

    virtual const char* to_string() {
        return "Char";
    }
    virtual bool support_operator(OperatorType ot);
};

// Type for *->*
class ArrowType : public Type {
private:
    static const int LEN = 128;
    Type* _src;
    Type* _dst;

public:
    ArrowType(Type* src, Type* dst) : Type(TYPE_ARROW),
        _src(src), _dst(dst) {};
    ~ArrowType();

    Type* src() { return _src; }
    Type* dst() { return _dst; }

    void set_src(Type* t) { _src = t; }
    void set_dst(Type* t) { _dst = t; }

    virtual const char* to_string();

    virtual bool equals(Type* t);
    virtual Type* try_apply(Type* left);
};

class Token;

class UserDefType : public Type {
private:
    char*  _name;

public:
    UserDefType(Token* t) : Type(TYPE_CONSTRUCTOR) {};
    ~UserDefType();

    const char* name() { return _name; }
};

class AnyType : public Type {
private:
    AnyType() : Type(TYPE_ANY) {}

public:
    static AnyType* get_instance() {
        static AnyType instance;
        return &instance;
    }

    virtual const char* to_string() {
        return "Any";
    }

    virtual bool equals(Type* t) {
        return this == t;
    }
};

class Node;
class TypeFunction : public Type {
private:
    vector<string>  _args;
    Node*           _body;

public:
    TypeFunction(Node* body) : Type(TYPE_FUNC),
        _body(body) {}

    vector<string>& args() { return _args; }
    Node* body()     { return _body; }
};

class GenericFuncType : public Type {
private:
    GenericFuncType() : Type(TYPE_GENERIC_FUNC) {}

public:
    static GenericFuncType* get_instance() {
        static GenericFuncType instance;
        return &instance;
    }

    virtual const char* to_string() {
        return "GenericFunc";
    }
};

class TypeVarType : public Type {
private:
    int _index;

public:
    TypeVarType() : Type(TYPE_VAR) {
        static int cnt = 0;
        _index = cnt++;
    }

    int index()     { return _index; }

    virtual const char* to_string() {
        return "TypeVar";
    }
};

class ArrayType : public Type {
private:
    Type*   _elm_type;
    int     _size;

public:
    ArrayType(Type* ety, int sz) : Type(TYPE_ARRAY),
        _elm_type(ety), _size(sz) {}

    static TypeFunction* make_function();
    Type* elm_type()    { return _elm_type; }
    int size()          { return _size; }

    virtual bool equals(Type* t);
};

class TypeDependentConst : public Type {
public:
    int     value;

    TypeDependentConst(int v) : Type(TYPE_DEPENDENT), value(v) {}
};

class TypeTable {
private:
    map<string, Type*>      _type_map;
    TypeTable*              _next;

    vector<string>          _generic_names;

public:
    TypeTable();
    TypeTable(TypeTable* parent);

    Type* get_expanded_type(string& name);
    map<string, Type*>& type_map()      { return _type_map; }
    TypeTable* next()                   { return _next; }

    void add(string& name, Type* t)     { _type_map[name] = t; }
    void add_generic_name(Node* n);
    void set_generic_name(int index, Type* t)   { _type_map[_generic_names[index]] = t; }
};

#endif

